use std::rc::{self};
use std::cell::{RefCell};
use std::any::{Any};
use std::marker::{PhantomData};
use weak_table::{PtrWeakKeyHashMap as WeakMap};

//////////////////////////////////////////////////////////////////////////////////////////
// Traits

/// Event that produces event argument of type A when fired.
pub trait Evt<A: ?Sized> {
  fn bind<S>(&self, sink: S) where S: Sink<A>;
}

/// Sink that may respond to events of type A.
pub trait Sink<A: ?Sized> {
  fn bind_to_src(self, src: EvtSrc<A>);

  fn bind_to<E>(self, evt: &E) where E: Evt<A>, Self: Sized
  { evt.bind(self) }
}

pub trait Callback<T, A: ?Sized>: 'static {
  fn call(&mut self, state: &mut T, arg: &A);
}

//////////////////////////////////////////////////////////////////////////////////////////

// EvtSrc

/// Source of events with event arguments type A.
pub struct EvtSrc<A: ?Sized>(PhantomData<A>, rc::Rc<()>);

impl<A: ?Sized> EvtSrc<A> {
  /// Create a new event source.
  pub fn new() -> Self {
    EvtSrc(PhantomData, rc::Rc::new(()))
  }

  pub fn id(&self) -> &rc::Rc<()> { &self.1 }
  pub fn into_id(self) -> rc::Rc<()> { self.1 }
}

impl<A> Evt<A> for EvtSrc<A> {
  fn bind<S>(&self, sink: S) where S: Sink<A> {
    sink.bind_to_src(EvtSrc::clone(self))
  }
}

impl<A> Clone for EvtSrc<A> {
  fn clone(&self) -> Self {
    EvtSrc(PhantomData, rc::Rc::clone(&self.1))
  }
}

impl<A> PartialEq for EvtSrc<A> {
  fn eq(&self, _: &Self) -> bool { true }
}

impl<A> Eq for EvtSrc<A> {}

//////////////////////////////////////////////////////////////////////////////////////////

struct DynCallback<T, A: ?Sized>(rc::Rc<RefCell<dyn Callback<T, A>>>);

// invariant:
//   all RHS bindings to this map should be type Callback<T, A> for some A
type InternalCallbackMap =
  rc::Rc<RefCell<WeakMap<rc::Weak<()>, Vec<Box<dyn Any>>>>>; // yikes

pub struct CallbackMap<T: 'static> {
  phantom: PhantomData<T>,
  map: InternalCallbackMap,
}

pub struct Dispatcher<'s, T: 'static> {
  state: &'s mut T,
  map: InternalCallbackMap,
}

pub struct Registrar<T: 'static> {
  phantom: PhantomData<T>,
  map: InternalCallbackMap,
}

//

impl<T, A: ?Sized> DynCallback<T, A> {
  fn new<C>(cb: C) -> Self where C: Callback<T, A> {
    DynCallback(rc::Rc::new(RefCell::new(cb)))
  }
}

impl<T, A: ?Sized> Clone for DynCallback<T, A> {
  fn clone(&self) -> Self {
    DynCallback(self.0.clone())
  }
}

impl<T> CallbackMap<T> {
  /// Create a new Dispatcher with given initial state and no bound callbacks.
  pub fn new() -> Self {
    CallbackMap {
      phantom: PhantomData,
      map: rc::Rc::new(RefCell::new(WeakMap::new())),
    }
  }

  pub fn dispatcher<'s>(&mut self, state: &'s mut T) -> Dispatcher<'s, T> {
    Dispatcher { state, map: self.map.clone() }
  }

  pub fn registrar(&mut self) -> Registrar<T> {
    Registrar { phantom: PhantomData, map: self.map.clone() }
  }
}

impl<'s, T> Dispatcher<'s, T> {
  /// Fire a given event (source) with given event args passed to each callback.
  pub fn fire<A: ?Sized>(&mut self, evt: &EvtSrc<A>, arg: &A)
  where A: 'static {
    let Dispatcher { state, map } = self;
    let callbacks: Vec<DynCallback<_, _>> =
      map.borrow_mut()
         .get_mut(evt.id())
         .into_iter()
         .flat_map(|rhs| rhs.iter_mut())
         .flat_map(|erased| erased.downcast_mut::<DynCallback<T, A>>().cloned())
         .collect();

    for cb in callbacks {
      cb.0.borrow_mut().call(state, arg);
    }
  }
}

impl<T> Registrar<T> {
  /// Create a `Sink<A>` from a callback. The callback can mutate the internal state
  /// of the listener.
  pub fn sink_from_callback<A, C>(&self, callback: C) -> impl Sink<A>
  where A: ?Sized + 'static,
        C: Callback<T, A> {
    CallbackSink { phantom: PhantomData, map: self.map.clone(), callback }
  }
}

struct CallbackSink<T, C> {
  phantom: PhantomData<fn(&mut T)>,
  map: InternalCallbackMap,
  callback: C,
}

impl<T, A, C> Sink<A> for CallbackSink<T, C>
where T: 'static,
      A: ?Sized + 'static,
      C: Callback<T, A> + 'static {
  fn bind_to_src(self, src: EvtSrc<A>) {
    bind(self.map, src, self.callback)
  }
}

fn bind<T, A, C>(map: InternalCallbackMap, src: EvtSrc<A>, cb: C)
where T: 'static,
      A: ?Sized + 'static,
      C: Callback<T, A> {
  map.borrow_mut()
     .entry(src.into_id())
     .or_insert_with(Vec::new)
     .push(Box::new(DynCallback::new(cb)));
}


//////////////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod test {
  use super::*;
  use std::mem::{size_of};

  #[test]
  fn test_static_sizes() {
    let ptr_size = size_of::<*const i32>();
    assert_eq!(size_of::<EvtSrc<()>>(), ptr_size);
    assert_eq!(size_of::<Option<EvtSrc<()>>>(), ptr_size);
  }

  #[test]
  fn test_listener_callback() {
    let mut dsp = Dispatcher::new(vec![]);
    let evt = EvtSrc::new();

    dsp.callback(|state, &arg| state.push(arg))
       .bind_to(&evt);

    dsp.fire(&evt, 1);
    dsp.fire(&evt, 2);
    dsp.fire(&evt, 3);
    assert_eq!(dsp.state(), &[1, 2, 3])
  }

  #[test]
  fn test_listener_heterog() {
    let mut dsp = Dispatcher::new(String::from(""));
    let evt1 = EvtSrc::new();
    let evt2 = EvtSrc::new();

    dsp.callback(|s, &arg| s.push_str(arg))
       .bind_to(&evt1);
    dsp.callback(|s, &arg| s.push_str(&format!("[{:?}]", arg)))
       .bind_to(&evt2);

    dsp.fire(&evt1, "hi");
    dsp.fire(&evt2, 5);
    dsp.fire(&evt1, "world");
    assert_eq!(dsp.state(), "hi[5]world");
  }
}
